﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO.Ports;

namespace GSMMODEM
{
    /// <summary>
    /// 电话本
    /// </summary>
    public class PhoneBook
    {
        private int _inedx = 0;

        public int Inedx
        {
            get { return _inedx; }
            set { _inedx = value; }
        }
        private int _numType = 0;

        public int NumType
        {
            get { return _numType; }
            set { _numType = value; }
        }
        private string _name = String.Empty;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        private string _number = String.Empty;

        public string Number
        {
            get { return _number; }
            set { _number = value; }
        }
    }
    /// <summary>
    ///设备类，完成短信发送 接收等
    /// </summary>
    public class GsmModem
    {
        #region 构造方法
        /// <summary>
        /// 无参数构造函数 完成有关初始化工作
        /// </summary>
        private GsmModem(string comPort, int baudRate, int readTimeout, bool rtsEnable)
        {
            _sp = new SerialPort
                      {
                          PortName = comPort,
                          BaudRate = baudRate,
                          ReadTimeout = readTimeout,
                          RtsEnable = rtsEnable
                      };
            _sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="comPort">串口号</param>
        /// <param name="baudRate">波特率</param>
        private GsmModem(string comPort, int baudRate) : this(comPort, baudRate, 25000, true) { ;}
        /// <summary>
        /// 创建实例
        /// </summary>
        /// <returns></returns>
        //public static GSMModem CreateInstance()
        //{
        //    if (Instance == null)
        //    {
        //        ReadWriteIni IniHelper = null;
        //        IniHelper = new ReadWriteIni(System.IO.Path.Combine(System.Windows.Forms.Application.StartupPath, configFile)); //读取配置文件
        //        string PortName =Convert.ToString(IniHelper.IniReadValue("GSMMODEM", "PortName", "COM1"));      //端口名称
        //        int BaudRate = Convert.ToInt32(IniHelper.IniReadValue("GSMMODEM", "BaudRate", "9600"));       //波特率
        //        int ReadTimeout  = Convert.ToInt32(IniHelper.IniReadValue("GSMMODEM", "ReadTimeout", "25000"));//读超时时间 发送短信时间的需要
        //        bool RtsEnable = Convert.ToBoolean(IniHelper.IniReadValue("GSMMODEM", "RtsEnable", "TRUE"));   //必须为true 这样串口才能接收到数据
        //        Instance = new GSMModem(PortName,BaudRate,ReadTimeout,RtsEnable);
        //    }
        //    return Instance;
        //}
        /// <summary>
        /// 创建实例
        /// </summary>
        /// <param name="comPort"></param>
        /// <param name="baudRate"></param>
        /// <returns></returns>
        public static GsmModem GetInstance(string comPort, int baudRate)
        {

            if (ClassDict.ContainsKey(comPort) == true)
            {
                object obj;
                ClassDict.TryGetValue(comPort, out obj);
                _instance = obj as GsmModem;
            }
            else
            {
                _instance = new GsmModem(comPort, baudRate);
                ClassDict.Add(comPort, _instance);
            }
            return _instance;
        }
        ~GsmModem()
        {
            if (IsOpen == true)
            {
                this.Close();                
            }
        }
        #endregion

        #region AT命令

        public struct ATCommand
        {
            /// <summary>
            /// 发送短信
            /// </summary>
            public const string CTRLZ = "\x01a";
            /// <summary>
            /// 换行输入(下一行内容)
            /// </summary>
            public const string LINE = "\r{0}";
            /// <summary>
            /// 短信前缀
            /// </summary>
            public const string CMTI = "+CMTI:";
            /// <summary>
            /// 初始化端口(ATZ)
            /// </summary>
            public const string ATZ = "ATZ";
            /// <summary>
            /// 初始化端口(ATE0)
            /// </summary>
            public const string ATE = "ATE0";
            /// <summary>
            /// 查询信号质量
            /// </summary>
            public const string CSQ = "AT + CSQ";
            /// <summary>
            /// 获取短信中心地址(CSCA)
            /// </summary>
            public const string CSCA = "AT + CSCA?";
            /// <summary>
            /// 获取机器码
            /// </summary>
            public const string CGMI = "AT + CGMI";
            /// <summary>
            /// PDU编码格式(CMGF=0)
            ///TEXT编码格式(CNGF=1)
            /// </summary>
            public const string CMGF = "AT + CMGF = {0}";
            /// <summary>
            /// 错误报告结果码显示(CMEE=1)
            /// </summary>
            public const string CMEE = "AT + CMEE = 1";
            /// <summary>
            /// 新短信提示(CNMI=2,1)
            /// </summary>
            public const string CNMI = "AT + CNMI = 2,1";
            /// <summary>
            /// PDU设置通信内容长度(PDU编码方式通信内容长度)
            /// TEXT设置通信内容长度(11位电话号码)
            /// 0 REC UNREAD 接收未读
            /// 1 REC READ 接收已读
            /// 2 STO UNSENT 存储未发送
            /// 3 STO SENT 存储已发送
            /// 4 ALL 所有消息
            /// </summary>
            public const string CMGS = "AT + CMGS = {0}";
            /// <summary>
            /// 删除一条短信息(信息序号码)
            /// </summary>
            public const string CMGD = "AT + CMGD = {0}";
            /// <summary>
            /// 读取短信(读取第几条短信)
            /// </summary>
            public const string CMGR = "AT + CMGR = {0}";
            /// <summary>
            /// 获取短信列表
            /// </summary>
            public const string CMGL = "AT + CMGL = {0}";
            /// <summary>
            /// 查询电话本
            /// </summary>
            public const string CPBS = "AT + CPBS = {0}";
            /// <summary>
            /// 读取电话本
            /// </summary>
            public const string CPBR = "AT + CPBR = {0}";
        }

        #endregion

        #region 变量,属性
        private readonly SerialPort _sp = null;                //私有字段 串口对象

        private static GsmModem _instance = null;

        private const string _OK = "OK";

        private const string _ERROR = "ERROR";

        private const string _lock_ = "_lock_";

        private static readonly Dictionary<string, object> ClassDict = new Dictionary<string, object>();

        /// <summary>
        /// 设备是否打开
        /// 对串口IsOpen属性访问
        /// </summary>
        public bool IsOpen
        {
            get
            {
                return _sp.IsOpen;
            }
        }

        public bool AutoDelMsg
        {
            get;
            set;
        }
        #endregion

        #region 事件,委托

        /// <summary>
        /// 创建事件收到信息的委托
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="sms"></param>
        public delegate void OnRecievedHandler(object sender, SmsInfo sms);

        /// <summary>
        /// 收到短信息事件 OnRecieved 
        /// 收到短信将引发此事件
        /// </summary>
        public event OnRecievedHandler OnRecieved;

        /// <summary>
        /// 从串口收到数据 串口事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            string temp = _sp.ReadLine();

            //判断前缀是否为新短消息
            if (temp.Length > 8 && temp.Substring(0, 6) == ATCommand.CMTI)
            {
                //存储新信息序号
                int newMsgIndex = Convert.ToInt32(temp.Split(',')[1]);
                if (OnRecieved != null)
                {
                    //触发事件
                    OnRecieved(this, ReadMsg(newMsgIndex));
                }
            }
            //
        }
        #endregion

        #region 端口方法
        /// <summary>
        /// 设备初始化函数
        /// </summary>
        public bool Init()
        {
            bool retValue = false;
            //检查是否已经打开端口
            if (_sp.IsOpen == false)
            {
                try
                {
                    _sp.Open();  //打开通讯端口
                    //初始化端口
                    SendAT(ATCommand.ATZ);
                    SendAT(ATCommand.ATE);
                    //设置编码格式为 PDU
                    SendAT(ATCommand.CMGF, 0);
                    //新消息产生事件
                    SendAT(ATCommand.CNMI);
                    //没有异常: 初始化成功
                    retValue = true;
                }
                catch
                {
                    //异常: 初始化失败
                    retValue = false;
                }
            }
            return retValue;
        }

        /// <summary>
        /// 设备关闭函数
        /// </summary>
        /// <returns>是否成功</returns>
        public bool Close()
        {
            bool retValue = false;
            try
            {
                //检查是否已经打开端口
                if (_sp.IsOpen)
                {
                    //关闭端口
                    _sp.Close();
                    //再次检查端口状态
                    retValue = !_sp.IsOpen;
                }
            }
            catch (Exception)
            {
                retValue = false;
            }
            return retValue;
        }
        #endregion

        #region 公开函数
        /// <summary>
        /// 获取机器码
        /// </summary>
        /// <returns>机器码字符串（设备厂商，本机号码）</returns>
        public string GetMachineNo()
        {
            string result = SendAT(ATCommand.CGMI);
            if (result.Substring(result.Length - 4, 3).Trim() == _OK)
            {
                result = result.Substring(0, result.Length - 5).Trim();
            }
            else
            {
                result = string.Empty;
            }
            return result;
        }
        /// <summary>
        /// 获取设备信号强度
        /// </summary>
        /// <returns></returns>
        public int GetRssi()
        {
            int retValue = 99;
            string result = SendAT(ATCommand.CSQ);
            if (result.Substring(result.Length - 4, 3).Trim() == _OK)
            {
                result = result.Substring(7, 2).Trim();
                retValue = Convert.ToInt32(result);
            }
            return retValue;
        }
        /// <summary>
        /// 获取短信中心号码
        /// </summary>
        /// <returns></returns>
        public string GetMsgCenter()
        {
            string tmp = SendAT(ATCommand.CSCA);
            if (tmp.EndsWith(_OK))
            {
                tmp = tmp.Split('\"')[1].Trim();
            }
            return tmp;
        }

        /// <summary>
        /// 发送AT指令 逐条发送AT指令 调用一次发送一条指令
        /// 能返回一个OK或ERROR算一条指令
        /// </summary>
        /// <param name="ATCom">AT指令</param>
        /// <returns>发送指令后返回的字符串</returns>
        public string SendAT(string ATCom)
        {
            if (String.IsNullOrEmpty(ATCom))
            {
                return _ERROR;
            }
            lock (_lock_) //排他锁 ,防止修改AT命令
            {
                //
                string temp = string.Empty;
                //返回结果
                string result = string.Empty;
                //忽略接收缓冲区内容，准备发送
                _sp.DiscardInBuffer();
                //注销事件关联，为发送做准备
                _sp.DataReceived -= sp_DataReceived;
                try
                {
                    //发送AT指令
                    _sp.Write(ATCom + "\r");
                    //接收数据 循环读取数据 直至收到“OK”或“ERROR”
                    while (temp.Trim() != _OK && temp.Trim() != _ERROR)
                    {
                        temp = _sp.ReadLine(); //读取串口返回结果
                        result += temp;       //叠加返回值
                    }
                }
                catch
                {
                    result = _ERROR;
                }
                finally
                {
                    //事件重新绑定 正常监视串口数据
                    _sp.DataReceived += sp_DataReceived;
                }
                return result.Trim();
            }
        }
        /// <summary>
        /// 发送AT命令
        /// </summary>
        /// <param name="ATCom"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public string SendAT(string ATCom, params string[] args)
        {
            //按命令 混合参数
            string comm = string.Format(ATCom, args);
            //发送AT命令
            return SendAT(comm);                     
        }
        /// <summary>
        /// 发送整数参数命令
        /// </summary>
        /// <param name="ATCom">AT 指令</param>
        /// <param name="arg">参数</param>
        /// <returns>返回值</returns>
        public string SendAT(string ATCom, int arg)
        {
            return SendAT(ATCom, arg.ToString());    //发送AT命令
        }

        #endregion

        #region 短信方法
        /// <summary>
        /// 获取电话本资料
        /// </summary>
        /// <returns></returns>
        public List<PhoneBook> GetAllNumber()
        {
            List<PhoneBook> list = new List<PhoneBook>();

            string temp = SendAT(ATCommand.CPBR, "?").Split(',')[0];
            if(temp != _ERROR)
            {
                temp = temp.Substring(8, temp.Length - 9).Replace('-', ',');
                string[] str = SendAT(ATCommand.CPBR, temp).Split('\r');

                foreach (string s in str)
                {
                    if (!String.IsNullOrEmpty(s) && s != _OK && s != _ERROR)
                    {
                        string[] strlist = s.Split(',');
                        var pb = new PhoneBook
                                           {
                                               Inedx =
                                                   Convert.ToInt32(
                                                       strlist[0].Substring(6, strlist[0].Length - 6).Trim('"')),
                                               Number = strlist[1].Trim('"'),
                                               NumType = Convert.ToInt32(strlist[2]),
                                               Name = strlist[3].Trim('"')
                                           };
                        list.Add(pb);
                    }
                }
            }
            return list;

        }
        /// <summary>
        /// 发送短信
        /// </summary>
        /// <param name="phone">手机号码</param>
        /// <param name="msg">短信内容</param>
        public bool SendMsg(string phone, string msg)
        {
            int len = 0;
            string center = GetMsgCenter();
            string temp = PDUEncoder.Encoding(center, phone, msg, out len);   //编码
            temp = SendAT(ATCommand.CMGS, string.Concat(len, "\r", temp, ATCommand.CTRLZ));  //26 Ctrl+Z ascii码
            return temp.EndsWith(_OK);
        }
        /// <summary>
        /// 群发短信
        /// 将同一内容的信息发送至多部手机
        /// </summary>
        /// <param name="phones">手机号码集合</param>
        /// <param name="msg">信息</param>
        /// <returns>返回失败的号码集合</returns>
        public List<string> SendMsg(List<string> phones, string msg)
        {
            List<string> item = new List<string>();
            foreach (string phone in phones)
            {
                if (SendMsg(phone, msg) == false)
                {
                    item.Add(phone);
                }
            }
            return item;
        }

        /// <summary>
        /// 按序号读取短信
        /// 如果没有,返回Null
        /// </summary>
        /// <param name="index">序号</param>
        /// <returns>信息字符串 (中心号码，手机号码，发送时间，短信内容)</returns>
        public SmsInfo ReadMsg(int index)
        {
            string temp = SendAT(ATCommand.CMGR, index);
            if (temp == null || temp == _ERROR)
            {
                return null;
            }
            temp = temp.Split('\r')[1];//取出PDU串(char)(13)为0x0a即\r 按\r分为多个字符串 第3个是PDU串
            SmsInfo smsinfo = PDUDecoder.Decoding(temp);
            //自动删除已读取的短信
            if (AutoDelMsg == true)
            {
                DelMsg(index);
            }
            return smsinfo;
        }
        /// <summary>
        /// 获取未读信息列表
        /// </summary>
        /// <returns>未读信息列表（中心号码，手机号码，发送时间，短信内容）</returns>
        public IList<SmsInfo> GetUnreadMsg()
        {
            IList<SmsInfo> result = GetMsgByType(0);
            return result;
        }
        /// <summary>
        /// 获取所有信息 已读,未读
        /// </summary>
        /// <returns></returns>
        public IList<SmsInfo> GetAllMsg()
        {
            IList<SmsInfo> result = GetMsgByType(4);
            return result;
        }
        /// <summary>
        /// 根据类型获取短信
        /// </summary>
        /// <param name="type">类型</param>
        /// <returns>信息集合</returns>
        public IList<SmsInfo> GetMsgByType(int type)
        {
            string[] temp = null;
            IList<SmsInfo> result = new List<SmsInfo>();            
            string tmp =  SendAT(ATCommand.CMGL, type);
            if (tmp.EndsWith(_OK))
            {
                temp = tmp.Split('\r');
                foreach (string str in temp)
                {
                    if (!string.IsNullOrEmpty(str) && str.Substring(0, 2).Trim() != "+C" && str.Substring(0, 2) != _OK)
                    {
                        SmsInfo smsinfo = PDUDecoder.Decoding(str); //PDU解码
                        result.Add(smsinfo);
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// 删除短信 按索引位置
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public bool DelMsg(int index)
        {
            return SendAT(ATCommand.CMGD, index).Equals(_OK);
        }
        #endregion
    }
}
